import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList; //used to store lines and points of convex hull
import java.util.Formatter;
import javax.swing.JOptionPane; //used to create dialog boxes to get filenames

/**
 * @author Pedro J Toro
 * csc7013 Advanced Mathematics for Computer Scientists
 * This Program simulates the Diffie-Hellman key exchange protocol. It will ask for the number of users,
 * the public values, g and n (g^x mod n) and random numbers for each of the users. It will then
 * show the process of calculating the private key that will be used for encryption. The larger the
 * prime values for g and n, the better the results for the private key. The output is written to
 * the console as well as an output file.
 */

class csc7013 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		PrintWriter outFile = null; // handle to the output file
		try {
			outFile = new PrintWriter(new FileWriter(new File("outfile.txt"), true));
		} catch (IOException e1) {
			e1.printStackTrace();
			System.out.println("Problem with output file.");
			System.exit(1);
		}
		
		int numberOfUsers;
		int publicG = 0;
		int publicN = 0;
		boolean isPrime;
		ArrayList<DiffieHellman> users = new ArrayList<DiffieHellman>();
		
		// get number of users
		do {
			try {
				numberOfUsers = Integer.parseInt(JOptionPane.showInputDialog(null, "Enter number of users: "));
				
				if (numberOfUsers <= 0) {
					JOptionPane.showMessageDialog(null, "Please enter a number greater than 0.");
				}
			} catch (NumberFormatException e) {
				// exit if cancel was pressed
				if (e.getMessage().equals("null")) {
					System.exit(1);
				}
				
				JOptionPane.showMessageDialog(null, "Please enter a whole number greater.");
				numberOfUsers = 0;
			}
		} while (numberOfUsers <= 0);
				
		// get large prime number for 'g' (g^x mod n)
		do {
			try {
				publicG = Integer.parseInt(JOptionPane.showInputDialog(null, "Enter large prime number for 'g' (g^x mod n): "));
				isPrime = prime(publicG);
			
				if (!isPrime) {
					JOptionPane.showMessageDialog(null, "Number is not prime. Please re-enter a large prime number.");
				}	
			} catch (NumberFormatException e){
				// exit if cancel was pressed
				if (e.getMessage().equals("null")) {
					System.exit(1);
				}

				JOptionPane.showMessageDialog(null, "Please enter a whole number.");
				isPrime = false;
			}
		} while (!isPrime);
		
		// get large prime number for 'n' (g^x mod n)
		do {
			try {
				publicN = Integer.parseInt(JOptionPane.showInputDialog(null, "Enter large prime number for 'n' (g^x mod n): "));
				isPrime = prime(publicN);
			
				if (!isPrime) {
					JOptionPane.showMessageDialog(null, "Number is not prime. Please re-enter a large prime number.");
				}
			} catch (NumberFormatException e) {
				// exit if cancel was pressed
				if (e.getMessage().equals("null")) {
					System.exit(1);
				}

				JOptionPane.showMessageDialog(null, "Please enter a whole number.");
				isPrime = false;
			}
		} while (!isPrime);
		
		// print out to screen
		System.out.printf("Number of users: %d\n", numberOfUsers);
		System.out.printf("Public g: %d\n", publicG);
		System.out.printf("Public n: %d\n", publicN);
		// print out to file
		outFile.printf("Number of users: %d\n", numberOfUsers);
		outFile.printf("Public g: %d\n", publicG);
		outFile.printf("Public n: %d\n", publicN);
		
		int x, y;
		int tempNumber = 0;
		
		for (x = 0; x < numberOfUsers; x++) {
			users.add(new DiffieHellman(publicG, publicN));
			tempNumber = 0;
			do {
				try {
					tempNumber = Integer.parseInt(JOptionPane.showInputDialog(null, new Formatter().format("User %d, please enter a random number: ", x)));
					
					if (tempNumber <= 0) {
						JOptionPane.showMessageDialog(null, "Please enter a number greater than 0 (zero).");
					} else {
						users.get(x).setMyPower(tempNumber);
					}
				} catch (NumberFormatException e){
					// exit if cancel was pressed
					if (e.getMessage().equals("null")) {
						System.exit(1);
					}
					
					JOptionPane.showMessageDialog(null, "Please enter a whole number.");
				}
				
			} while (tempNumber <= 0);

			// print out to screen
			System.out.printf("User %d random number: %.0f\n", x, users.get(x).getMyPower());
			// print out to file
			outFile.printf("User %d random number: %.0f\n", x, users.get(x).getMyPower());
		}
		
		for (x = 0; x < numberOfUsers; x++) {
			System.out.printf("Round %d\n", x+1);
			outFile.printf("Round %d\n", x+1);
			
			for (y = 0; y < numberOfUsers; y++) {
				if (y < numberOfUsers - 1) {
					users.get(y+1).setPrivateKey(users.get(y).publicFunction(users.get(y).getMyBase()));
					if (x < numberOfUsers-1) {
						System.out.printf("   User %d sends User %d: %.0f\n", y, y+1, users.get(y+1).getPrivateKey());
						outFile.printf("   User %d sends User %d: %.0f\n", y, y+1, users.get(y+1).getPrivateKey());
					} else {
						// last round
						System.out.printf("   User %d computes: %.0f\n", y+1, users.get(y+1).getPrivateKey());
						outFile.printf("   User %d computes: %.0f\n", y+1, users.get(y+1).getPrivateKey());
					}
				} else {
					users.get(0).setPrivateKey(users.get(y).publicFunction(users.get(y).getMyBase()));
					if (x < numberOfUsers-1) {
						System.out.printf("   User %d sends User 0: %.0f\n", y, users.get(0).getPrivateKey());
						outFile.printf("   User %d sends User 0: %.0f\n", y, users.get(0).getPrivateKey());
					} else {
						// last round
						System.out.printf("   User 0 computes: %.0f\n", users.get(0).getPrivateKey());						
						outFile.printf("   User 0 computes: %.0f\n", users.get(0).getPrivateKey());
					}
				}
			}
			
			for (y = 0; y < numberOfUsers; y++) {
				users.get(y).swapBases();
			}
		}
		
		int count;
		String input, tempString;
		
		input = JOptionPane.showInputDialog(null, "Enter text to encrypt/decrypt: ");
		
		// print out to screen
		System.out.println("Encryption/Decryption");
		System.out.println("Original message: " + input);
		// print out to file
		outFile.println("-= Encryption/Decryption =-");
		outFile.println("Original message: " + input);
				
		for (x = 0; x < numberOfUsers; x++) {
			// print out to screen
			System.out.printf("   User %d: ", x);
			// print out to file
			outFile.printf("   User %d: ", x);
			
			count = 0;
			tempString = "";
			while (count < input.length()) {
				tempString = tempString + exclusiveOr(input.charAt(count), users.get(x).getPrivateKey());
				count++;
			}
			
			// print out to screen
			System.out.println(tempString);
			// print out to file
			outFile.println(tempString);			
		}
		
		// print out to screen
		System.out.println("-------------------------------");
		// print out to file
		outFile.println("-------------------------------");
		
		// close output file
		outFile.close();		
	}
	
	/**
	 * Exclusive OR
	 * @param myChar, character to change
	 * @param shift, change character by this much
	 * @return new character
	 */
	private static char exclusiveOr(char myChar, double shift) {
		return (char) (myChar ^ (int)shift);
	}
	
	/**
	 * Checks if given number is a prime number. 
	 * @param number, to check if prime
	 * @return true if number is prime, else otherwise
	 */
	private static boolean prime(int number) {
		if (number < 2) {
			// not prime
			return false;
		} else if ((number == 2) || (number == 3)) {
			// 2 and 3 are prime
			return true;
		} else if ((number % 2) == 0) {
			// divisible by 2, not prime 
			return false;
		}
		else {	
			// the Sieve of Eratosthenes, no need to look at numbers above divisor^2
			int divisor = 3;
			
			while (((divisor * divisor) < number) && ((number % divisor) > 0)) {
				divisor = divisor + 2;
			}
			
			if ((number % divisor) > 0) {
				// number is prime
				return true;
			} else {
				// not prime
				return false;
			}
		}
	}

}
